using System;
using System.CodeDom;
using System.Collections;
using System.Linq;
using System.Text.RegularExpressions;

namespace Think.Problems
{
    public enum CardColor
    {
        Black,
        Red
    }

    public enum CardSuit
    {
        Club,
        Diamond,
        Heart,
        Spade,
    }

    public static class Card
    {
        public const int Ace = 1;
        public const int Jack = 11;
        public const int Queen = 12;
        public const int King = 13;

        /// <summary>
        /// Determine if the given card name string (<paramref name="a_card"/>) is valid.
        /// </summary>
        /// <param name="a_card">Card name string to validate.</param>
        /// <returns>True if card is valid.</returns>
        public static bool IsValid(string a_card)
        {
            if (a_card == null)
                return false;

            return Regex.IsMatch(a_card, @"^([A23456789JKQ]|10)[HDCS]$");
        }

        /// <summary>
        /// Throw an exception if the given card () is not valid.
        /// </summary>
        /// <param name="a_card">Card name string.</param>
        public static void ThrowIfNotValid(string a_card)
        {
            if (!IsValid(a_card))
                throw new InvalidOperationException("Not a valid card ('" + a_card + "')!");
        }

        /// <summary>
        /// Get the color of the given card (<paramref name="a_card"/>).
        /// </summary>
        /// <param name="a_card">Card name string.</param>
        /// <returns>Color of the card.</returns>
        /// <exception cref="ArgumentNullException">Thrown if "<paramref name="a_card"/>" is null.</exception>
        public static CardColor Color(string a_card)
        {
            #region Argument Validation

            if (a_card == null)
                throw new ArgumentNullException("a_card");

            #endregion

            ThrowIfNotValid(a_card);

            if ("CS".Contains(a_card.Last()))
                return CardColor.Black;
            else if ("HD".Contains(a_card.Last()))
                return CardColor.Red;
            else
                throw new InvalidOperationException("Unrecognized suit ('" + a_card[1] + "')!");
        }

        /// <summary>
        /// Get the suit of the given card (<paramref name="a_card"/>).
        /// </summary>
        /// <param name="a_card">Card name string.</param>
        /// <returns>Suite of the card.</returns>
        /// <exception cref="ArgumentNullException">Thrown if "<paramref name="a_card"/>" is null.</exception>
        public static CardSuit Suit(string a_card)
        {
            #region Argument Validation

            if (a_card == null)
                throw new ArgumentNullException("a_card");

            #endregion

            ThrowIfNotValid(a_card);

            var suit = GetSuitCode(a_card);

            if (suit == "C")
                return CardSuit.Club;
            if (suit == "S")
                return CardSuit.Spade;
            if (suit == "H")
                return CardSuit.Heart;
            if (suit == "D")
                return CardSuit.Diamond;
            else
                throw new InvalidOperationException("Unrecognized suit ('" + a_card[1] + "')!");
        }

        /// <summary>
        /// Get the rank value for the given card name string (<paramref name="a_card"/>).
        /// </summary>
        /// <param name="a_card">Card name string.</param>
        /// <returns>Rank value.</returns>
        /// <exception cref="ArgumentNullException">Thrown if "<paramref name="a_card"/>" is null.</exception>
        public static int RankValue(string a_card)
        {
            #region Argument Validation

            if (a_card == null)
                throw new ArgumentNullException("a_card");

            #endregion

            ThrowIfNotValid(a_card);

            var rankCode = GetRankCode(a_card);

            if (rankCode == "A")
                return 1;
            if (rankCode == "J")
                return 11;
            if (rankCode == "Q")
                return 12;
            if (rankCode == "K")
                return 13;

            return Int32.Parse(rankCode);
        }

        /// <summary>
        /// Get the rank code for the given card name string (<paramref name="a_card"/>).
        /// </summary>
        /// <param name="a_card">Card name string.</param>
        /// <returns>Rank code.</returns>
        private static string GetRankCode(string a_card)
        {
            var match = Regex.Match(a_card, @"^([A23456789JKQ]|10)[HDCS]$");

            return match.Groups[1].Value;
        }

        /// <summary>
        /// Get the suit code for the given card name string (<paramref name="a_card"/>).
        /// </summary>
        /// <param name="a_card">Card name string.</param>
        /// <returns>Suit code.</returns>
        private static string GetSuitCode(string a_card)
        {
            var match = Regex.Match(a_card, @"^(?:[A23456789JKQ]|10)([HDCS])$");

            return match.Groups[1].Value;
        }

        /// <summary>
        /// Get all valid cards.
        /// </summary>
        /// <returns>Array of all valid cards.</returns>
        public static string[] Cards()
        {
            return new [] 
            {
                "AH", "2H", "3H", "4H", "5H", "6H", "7H", "8H", "9H", "10H", "JH", "QH", "KH",
                "AC", "2C", "3C", "4C", "5C", "6C", "7C", "8C", "9C", "10C", "JC", "QC", "KC",
                "AD", "2D", "3D", "4D", "5D", "6D", "7D", "8D", "9D", "10D", "JD", "QD", "KD",
                "AS", "2S", "3S", "4S", "5S", "6S", "7S", "8S", "9S", "10S", "JS", "QS", "KS",
            };
        }

        /// <summary>
        ///  Get all valid cards with the given suit (<paramref name="a_suit"/>).
        /// </summary>
        /// <param name="a_suit">Suit.</param>
        /// <returns>Cards of the suit.</returns>
        public static string[] Cards(CardSuit a_suit)
        {
            return Cards().Where(i => Suit(i) == a_suit).ToArray();
        }

        /// <summary>
        /// Get all valid cards with the given rank (<pramref name="a_rankValue"/>).
        /// </summary>
        /// <param name="a_rankValue">Rank.</param>
        /// <returns>Cards of the rank.</returns>
        public static string[] Cards(int a_rankValue)
        {
            return Cards().Where(i => RankValue(i) == a_rankValue).ToArray();
        }
        
        /// <summary>
        /// Create a card from the given suit (<paramref name="a_suit"/>) and rank (<paramref name="a_rankValue"/>).
        /// </summary>
        /// <param name="a_suit">Suit.</param>
        /// <param name="a_rankValue">Rank.</param>
        /// <returns>Created card.</returns>
        public static string Create(CardSuit a_suit, int a_rankValue)
        {
            if (a_rankValue <= 0 || a_rankValue > 13)
                throw new ArgumentOutOfRangeException("a_rankValue");

            const string ranks = "A23456789XJQK";

            var rank = ranks[a_rankValue - 1].ToString();
            if (rank == "X")
                rank = "10";

            return String.Format("{0}{1}", rank, a_suit.ToString()[0]);
        }

    }
}